package stupidMineralCollector.model;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;

import stupidMineralCollector.Constants;

/**
 * Created by Stefan Rudolph on 03.03.14.
 */
public class PheromoneMap {

	private volatile double[][] pheromoneValues;

	public PheromoneMap(int sizeX, int sizeY) {
		pheromoneValues = new double[sizeX][sizeY];
	}

	/**
	 * Use this to get the pheromone value to a position on the map.
	 * 
	 * @param x
	 *            The tile x coordinate
	 * @param y
	 *            The tile y coordinate
	 * @return The pheromone value or 0.0 if it is not on the map
	 */
	public synchronized double getScent(int x, int y) {
		int indexX = x;// / 32;
		int indexY = y;// / 32;

		if (indexX < pheromoneValues.length && indexX >= 0
				&& indexY < pheromoneValues[0].length && indexY >= 0) {
			return pheromoneValues[indexX][indexY];
		} else {
			return 0.0;
		}
	}

	/**
	 * Applies diffusion and evaporation to the pheromones
	 */
	public synchronized void diffuseAndEvaporatePheromones() {
		diffusePheromones();
		evaporatePheromones();
	}

	/**
	 * Increases the Pheromone Level at a specific point.
	 * 
	 * @param x
	 *            The x coordinate.
	 * @param y
	 *            The y coordinate
	 */
	public synchronized void increasePheromones(int x, int y) {
		// TODO: Has to be implemented. Use the pheromoneValues array.
		pheromoneValues[x][y] += Constants.getInstance().getPheromoneOutput();
	}
	
	/**
	 * Increases the Pheromone Level at a specific point depending on a distance value.
	 * 
	 * @param x
	 *            The x coordinate.
	 * @param y
	 *            The y coordinate
	 * @param percent
	 * 			  The traveled distance in percent (to the command center)       	
	 */
	public synchronized void increasePheromones(int x, int y, double percent) {
		// TODO: Has to be implemented. Use the pheromoneValues array.
		Constants c = Constants.getInstance();
		pheromoneValues[x][y] += Math.max(c.getMinPheromoneLevel(), 
				c.getPheromoneOutput() * (1.0-percent));
	}

	private void diffusePheromones() {
		double[][] newPheromoneValues = new double[pheromoneValues.length][pheromoneValues[0].length];
		// TODO: Write the new pheromone Values in the newPheromoneValues array
		// Notice: You have to set diffuionRate in the Config class
		double diffusionRate = Constants.getInstance().getDiffusionRate();
		for( int x = 0; x < this.pheromoneValues.length; ++x )
		{
			for( int y = 0; y < this.pheromoneValues[x].length; ++y )
			{
				// ph-value at this patch
				double ph = this.pheromoneValues[x][y];
				// skip fields with 0 ph values
				if( ph == 0 )
					continue;
				// diffuse to all 8 neighbors (less if map borders...)
				ArrayList<Point> neighbors = getNeighbors(x,y);
				double diffuseValue = (ph/neighbors.size()) * diffusionRate;
				for( Point p :neighbors )
				{
					newPheromoneValues[p.x][p.y] += diffuseValue;
				}
				// remove diffused pheromones
				newPheromoneValues[x][y] += ph - (diffuseValue * neighbors.size());
			}
		}

		this.pheromoneValues = newPheromoneValues;
	}
	
	private ArrayList<Point> getNeighbors(int x, int y)
	{
		ArrayList<Point> result = new ArrayList<Point>();
		for( int i = -1; i <= 1; ++i )
		{
			for( int j = -1; j <= 1; ++j )
			{
				int tempX = x + i;
				int tempY = y + j;
				// check for invalid coords
				if(tempX < 0 || tempX >= this.pheromoneValues.length
						|| tempY < 0 || tempY >= this.pheromoneValues[x].length)
					continue;
				// don't diffuse to myself
				if(i == 0 && j == 0)
					continue;
				// add neighbor
				result.add(new Point(tempX,tempY));
			}
		}
		return result;
	}
	
	private void evaporatePheromones() {

		// TODO: Apply evaporation to the pheromones. Use the pheromoneValues
		// array.
		// Notice: You have to set the evaporationRate in the Config class
		double inverseEvaporationRate = 1.0 - Constants.getInstance().getEvaporationRate();
		for( int i = 0; i < this.pheromoneValues.length; ++i )
		{
			for( int j = 0; j < this.pheromoneValues[i].length; ++j )
			{
				this.pheromoneValues[i][j] *= inverseEvaporationRate;
			}
		}
	}

	/**
	 * Returns a copy of the pheromone array. Is used by the GUI.
	 * 
	 * @return a copy of the pheromone array.
	 */
	public synchronized double[][] getCopyOfPheromoneValues() {
		return Arrays.copyOf(pheromoneValues, pheromoneValues.length);
	}

	public synchronized String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("--------------------------------------");
		sb.append("\n");
		for (int i = 0; i < pheromoneValues.length; i++) {
			sb.append(Arrays.toString(pheromoneValues[i]));
			sb.append("\n");
		}
		return sb.toString();
	}

}
